//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#ifndef NJ_MUTEX_H
#	define NJ_MUTEX_H

#	include "NjNinjaSyncLib.h"
#	include "NjLocker.h"
#	include "NjAtomic.h"
#	include "NjEventsSystem.h"
#	include "NjMutex_config.h"

	/// \brief Implements a not-recursive mutual exclusion synchronization object
	///
	/// Mutual exclusion synchronization objects are typically used to avoid that two threads concurrently use the same shared resource
	///
	/// Being not-recursive means that if the same thread locks the same mutex twice, this will cause a deadlock
	///
	/// NjRecursiveMutex is similar to NjMutex and also support recursion
	class NjMutex
	{
	public:
		/// \brief Defines a scoped-lock for NjMutex
		typedef NjLocker<NjMutex> Locker;

		/// \brief Defines the timeout type
		typedef NjEventsSystem::TimeMs TimeMs;

		/// \brief Creates a NjMutex instance
		NjMutex();

		/// \brief Destroys this NjMutex instance
		~NjMutex();

		/// \brief Acquires the mutex and put it in locked state
		/// 
		/// At any time at maximum just one thread can acquire the mutex.
		/// The other threads will be suspended until the mutex is released by  an `Unlock` call
		void Lock();

		/// \brief Attempts to acquire the mutex and put it in locked state
		///
		/// TryLock(0) will attempt to acquire the mutex and will return immediately if the mutex is already locked, without waiting further.
		/// TryLock(NjEventsSystem::WAIT_FOREVER) is identical to Lock
		/// \param uTimeMs The time out in milliseconds
		/// \return If the mutex is acquired within \em uTimeMs milliseconds, TryLock will return NJ_TRUE otherwise it will return NJ_FALSE.
		NjBool TryLock(TimeMs uTimeMs);

		/// \brief Releases a previously acquired mutex. Just the thread that initially successfully acquired the mutex can call `Unlock`. This condition is enforced in some build configurations through assertions
		void Unlock();

	private:
		/// \brief The kernel event type
		typedef NjEventsSystem::Event Event;

		/// \brief Internal function called by TryLock
		/// \param uTimeMs The time out in milliseconds
		/// \param uThreadDebugId The thread id (for debug purpoese). This parameter is available just in some build configurations
		NjBool TryLockInternal(TimeMs uTimeMs
#if NJ_MUTEX_TRACKING_ENABLED
									, NjUint uThreadDebugId
#endif // NJ_MUTEX_TRACKING_ENABLED
								);

		/// \brief The bit used to mark if the NjMutex is locked or not
		static const NjUint STATE_MUTEX_LOCKED_BIT = 0;

		/// \copydoc STATE_MUTEX_LOCKED_BIT
		static const NjUint STATE_MUTEX_LOCKED     = 0x00000001;

		/// \brief Used to store the state of the kernel event object
		///
		/// This bit can be set only if the kernel object is allocated.
		/// When this bit is set it is expected that NjEventsSystem::TryWaitEvent operations will block the calling thread
		static const NjUint STATE_EVENT_UNSET      = 0x00000002;

		/// \brief A tag counter used to avoid the ABA problem
		/// \sa http://en.wikipedia.org/wiki/ABA_problem
		static const NjUint STATE_ABA_COUNTER_ONE  = 0x00000004;

		/// \brief The current state of the mutex
		NjAtomic<NjUint> m_kState;

		/// \brief The kernel event assigned to this mutex
		NjAtomic<Event *> m_kEvent;

#	if NJ_MUTEX_DETECT_DEADLOCKS_ENABLED
		/// \brief Private support class used to detect deadlocks
		class DeadlockDetection;
		NjMutex * * m_pDeadlockDetectionOwningThread;
#	endif // NJ_MUTEX_DETECT_DEADLOCKS_ENABLED

#	if NJ_MUTEX_STORE_LOCKER_ENABLED
		/// \brief Stores the current thread locking the resource
		NjAtomic<NjUint> m_kThreadDebugId;
#	endif // NJ_MUTEX_STORE_LOCKER_ENABLED

#	if NJ_MUTEX_STORE_USERS_ENABLED
		/// \brief Called immediately entering into a method
		void UserEnter();

		/// \brief Called before leaving a method
		void UserLeave();

		/// \brief This counter stores the current threads accessing to this resource
		NjAtomic<NjUint> m_kUsers;
#	endif // NJ_MUTEX_STORE_USERS_ENABLED

#	if NJ_MUTEX_TRACKING_ENABLED
		/// \brief Class to track mutexes usage
		class TrackerEntry;

		/// \copydoc TrackerEntry
		class Tracker;
#	endif // NJ_MUTEX_TRACKING_ENABLED

		NJ_DISABLE_COPY(NjMutex);
	};

#	include "NjMutex_inline.h"

#endif // NJ_MUTEX_H
